﻿using System;
using System.Xml;
using Notoric.Model.Collections;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// Describes a repetition (including its endings).
    /// </summary>
    public class RepetitionElement : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="RepetitionElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="CompositionElement"/>
        /// </para>
        /// <para>
        /// The composition that owns this repetition.
        /// </para>
        /// </param>
        /// <param name="firstMeasure">
        /// <para>
        /// Type: <see cref="System.UInt16"/>
        /// </para>
        /// <para>
        /// The number of the first (opening) measure of the repetition.
        /// </para>
        /// </param>
        /// <param name="lastMeasure">
        /// <para>
        /// Type: <see cref="System.UInt16"/>
        /// </para>
        /// <para>
        /// The number of the last (closing) measure of the repetition.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ValidationException">
        /// The value of the <paramref name="firstMeasure"/> or <paramref name="lastMeasure"/> is not valid.
        /// Please, refer to the documentation of the respective properties for details.
        /// </exception>
        internal RepetitionElement(CompositionPart packagePart, CompositionElement owner, MeasureElement firstMeasure, MeasureElement lastMeasure)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");
            if (firstMeasure == null)
                throw new ArgumentNullException("firstMeasure");
            if (lastMeasure == null)
                throw new ArgumentNullException("lastMeasure");
            if (firstMeasure.FindOwner<CompositionElement>() != owner || lastMeasure.FindOwner<CompositionElement>() != owner)
                throw new ArgumentException("The first and last measures must belong to the composition that owns this repetition.");
            if (firstMeasure.Number > lastMeasure.Number)
                throw new ArgumentException("The first measure must preceed the last measure.");

            FirstMeasure = firstMeasure;
            LastMeasure = lastMeasure;

            endings = new ElementCollection<EndingElement>(this, endingsElement);
        }

        /// <summary>
        /// Creates a new <see cref="RepetitionElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="CompositionElement"/>
        /// </para>
        /// <para>
        /// The composition that owns this repetition.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        internal RepetitionElement(CompositionPart packagePart, CompositionElement owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            firstMeasureAttribute = element.Attributes[PackageVocabulary.FirstMeasureAttribute];
            lastMeasureAttribute = element.Attributes[PackageVocabulary.LastMeasureAttribute];
            endingsElement = element[PackageVocabulary.EndingElementName + "s", PackageVocabulary.PackageNamespaceUri];

            endings = new ElementCollection<EndingElement>(this, endingsElement);
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.RepetitionElementName,
                PackageVocabulary.PackageNamespaceUri
                );

            firstMeasureAttribute = document.CreateAttribute(PackageVocabulary.FirstMeasureAttribute);
            lastMeasureAttribute = document.CreateAttribute(PackageVocabulary.LastMeasureAttribute);

            endingsElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.EndingElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );

            element.Attributes.Append(firstMeasureAttribute);
            element.Attributes.Append(lastMeasureAttribute);           
            element.AppendChild(endingsElement);

            return element;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the collection of repetition endings.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ElementCollection{T}"/> of <see cref="EndingElement"/>
        /// </para>
        /// <para>
        /// The collection of repetition endings.
        /// </para>
        /// </value>
        public ElementCollection<EndingElement> Endings
        {
            get { return endings; }
        }

        /// <summary>
        /// Gets or sets the first (opening) measure in the repetition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The first measure of this repetition (the one that opens the repetition).
        /// </para>
        /// <para>
        /// The repetition is in fact a continuous range of measures [<see cref="FirstMeasure"/>, <see cref="LastMeasure"/>].
        /// Repetitions cannot overlap with other repetitions.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> does not belong to the composition.<br/>
        /// -or-<br/>
        /// The <paramref name="value"/> is greater than <see cref="LastMeasure"/>.<br/>
        /// -or-<br/>
        /// The range [<paramref name="value"/>, <see cref="LastMeasure"/>] overlaps with another repetition.
        /// </exception>
        /// <seealso cref="LastMeasure"/>
        /// <seealso cref="RepetitionElement"/>
        /// <seealso cref="CompositionElement"/>
        /// TODO: Write the validation rules
        public MeasureElement FirstMeasure
        {
            get 
            {
                if (firstMeasure == null)
                    firstMeasure = PackagePart.Package.IDMap.FindObject<MeasureElement>(new Guid(firstMeasureAttribute.Value));

                return firstMeasure;
            }
            set
            {
                ValidateAndThrow("FirstMeasure", value);

                firstMeasure = value;
                firstMeasureAttribute.Value = value.ID.ToString();
                RaisePropertyChanged("FirstMeasure");
            }
        }

        /// <summary>
        /// Gets or sets the last (closing) measure in the repetition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="MeasureElement"/>
        /// </para>
        /// <para>
        /// The last measure of this repetition (the one that closes the repetition).
        /// </para>
        /// <para>
        /// The repetition is in fact a continuous range of measures [<see cref="FirstMeasure"/>, <see cref="LastMeasure"/>].
        /// Repetitions cannot overlap with other repetitions.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> does not belong to the composition.<br/>
        /// -or-<br/>
        /// The <paramref name="value"/> is less than <see cref="FirstMeasure"/>.<br/>
        /// -or-<br/>
        /// The range [<see cref="FirstMeasure"/>, <paramref name="value"/>] overlaps with another repetition.
        /// </exception>
        /// <seealso cref="FirstMeasure"/>
        /// <seealso cref="RepetitionElement"/>
        /// <seealso cref="CompositionElement"/>
        /// TODO: Write the validation rules
        public MeasureElement LastMeasure
        {
            get 
            {
                if (lastMeasure == null)
                    lastMeasure = PackagePart.Package.IDMap.FindObject<MeasureElement>(new Guid(lastMeasureAttribute.Value));

                return lastMeasure;
            }
            set
            {
                ValidateAndThrow("LastMeasure", value);

                lastMeasure = value;
                lastMeasureAttribute.Value = value.ID.ToString();
                RaisePropertyChanged("LastMeasure");
            }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="Endings"/> property.
        /// </summary>
        private ElementCollection<EndingElement> endings;
        /// <summary>
        /// Holds the XML element that holds the <see cref="Endings"/> collection.
        /// </summary>
        private XmlElement endingsElement;
        /// <summary>
        /// Holds a reference to the first measure.
        /// </summary>
        private MeasureElement firstMeasure;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="FirstMeasure"/> property.
        /// </summary>
        private XmlAttribute firstMeasureAttribute;
        /// <summary>
        /// Holds a reference to the last measure.
        /// </summary>
        private MeasureElement lastMeasure;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="LastMeasure"/> property.
        /// </summary>
        private XmlAttribute lastMeasureAttribute;

        #endregion
    }
}
